package org.vossnet.sfig;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static java.lang.String.format;
import org.apache.log4j.Logger;
import org.antlr.runtime.tree.CommonTree;

/*  From: spring bean 2.0 dtd

        Default values for all bean definitions. Can be overridden at
        the "bean" level. See those attribute definitions for details.

    <!ATTLIST bean scope CDATA #IMPLIED>    (singleton | prototype) "singleton"

    <!ATTLIST beans default-lazy-init (true | false) "false">
    <!ATTLIST bean  lazy-init (true | false | default) "default">

    <!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
    <!ATTLIST bean  autowire (no | byName | byType | constructor | autodetect | default) "default">

    <!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
    <!ATTLIST bean  dependency-check (none | objects | simple | all | default) "default">

    <!ATTLIST beans default-init-method CDATA #IMPLIED>
    <!ATTLIST bean  init-method CDATA #IMPLIED>

    <!ATTLIST beans default-destroy-method CDATA #IMPLIED>
    <!ATTLIST bean  destroy-method CDATA #IMPLIED>

    <!ATTLIST bean factory-method CDATA #IMPLIED>

    <!ATTLIST bean factory-bean CDATA #IMPLIED>
*/

public class BeanDef {
    private static final Logger log = Logger.getLogger(BeanDef.class);

    protected final String      beanID;
    protected CommonTree        ast_node = null;
    protected Object            beanInst = null;
    protected boolean			attributesSet = false;
    protected Scope             scope = Scope.SINGLETON;
    protected boolean           lazyInit = false;
    protected AutoWire          autoWire = AutoWire.NO;
    protected DependencyCheck   dependencyCheck = DependencyCheck.NONE;
    protected Object            initMethod;
    protected Object            destroyMethod;
    protected Object            factoryMethod;
    protected String            factoryBean;

    // constructor
    public BeanDef(String beanID,CommonTree ast_node) {
        this.beanID = beanID;
        this.ast_node = ast_node;
    }

    // JavaBean getters and setters
    public String getBeanID() {
        return beanID;
    }
    public CommonTree getAst_node() {
        return ast_node;
    }
    public void setAst_node(CommonTree ast_node) {
        this.ast_node = ast_node;
    }
    public Object getBeanInstance() {
        return this.beanInst;
    }
    public void setBeanInstance(Object bean) {
        this.beanInst = bean;
        this.ast_node = null;
        this.scope = Scope.SINGLETON;
    }
    public boolean areAttributesSet() {
		return attributesSet;
	}
	public void setAttributesSet(boolean attributesSet) {
		this.attributesSet = attributesSet;
	}
	public Scope getScope() {
        return scope;
    }
    public void setScope(Scope scope) {
        this.scope = scope;
    }
    public boolean isSingleton() {
        return this.scope == Scope.SINGLETON;
    }
    public boolean isLazyInit() {
        return lazyInit;
    }
    public void setLazyInit(boolean lazyInit) {
        this.lazyInit = lazyInit;
    }
    public AutoWire getAutoWire() {
        return autoWire;
    }
    public void setAutoWire(AutoWire autoWire) {
        this.autoWire = autoWire;
    }
    public DependencyCheck getDependencyCheck() {
        return dependencyCheck;
    }
    public void setDependencyCheck(DependencyCheck dependencyCheck) {
        this.dependencyCheck = dependencyCheck;
    }
    protected static String getMethodName(Object methObj) {
        return methObj == null
                    ? null : methObj instanceof String ?
                                (String) methObj : ((Method) methObj).getName();
    }
    protected Object getMethod(Object methObj,Object bean) throws SecurityException, NoSuchMethodException {
        return (methObj != null && methObj instanceof String)
                    ? bean.getClass().getMethod((String) methObj, (Class<?>[]) null) : methObj;
    }
    public Method getInitMethod(final Object bean) throws SecurityException, NoSuchMethodException {
        return (Method) (this.initMethod = getMethod(this.initMethod,bean));
    }
    public void setInitMethod(String initMethod) {
        this.initMethod = initMethod;
    }
    public Method getDestroyMethod(final Object bean) throws SecurityException, NoSuchMethodException {
        return (Method) (this.destroyMethod = getMethod(this.destroyMethod,bean));
    }
    public void setDestroyMethod(String destroyMethod) {
        this.destroyMethod = destroyMethod;
    }
    public Method getFactoryMethod(final Object bean) throws SecurityException, NoSuchMethodException {
        return (Method) (this.factoryMethod = getMethod(this.factoryMethod,bean));
    }
    public void setFactoryMethod(String factoryMethod) {
        this.factoryMethod = factoryMethod;
    }
    public String getFactoryBean() {
        return factoryBean;
    }
    public void setFactoryBean(String factoryBean) {
        this.factoryBean = factoryBean;
    }

    // can be used by debug logging to dump the state of a BeanDef object
    public String toString() {
        final StringBuilder sbuf = new StringBuilder(super.toString());
        final Field[] fields = this.getClass().getDeclaredFields();
        final String lineEnd = System.getProperty("line.separator");
        sbuf.append(':').append(lineEnd);
        for (Field field : fields) {
            final String name = field.getName();
            try {
                sbuf.append("\t").append(name).append(": ").append(field.get(this)).append(lineEnd);
            } catch (IllegalArgumentException e) {
                sbuf.append("*** invalid field ").append(name).append(" *** - ")
                        .append(e.getClass().getSimpleName()).append(lineEnd);
                continue;
            } catch (IllegalAccessException e) {
                sbuf.append("*** invalid field ").append(name).append(" *** - ")
                        .append(e.getClass().getSimpleName()).append(lineEnd);
                continue;
            }
        }
        return sbuf.toString();
    }

    public static enum Scope {
        SINGLETON("singleton"), PROTOTYPE("prototype");

        private static final Map<String,Scope> lookup = new HashMap<String,Scope>();
        static {
            for(Scope e : values())
                lookup.put(e.usageName,e);
        }
        private final String usageName;
        private Scope(String usageName) { this.usageName = usageName; }
        public String toString() { return usageName; }
        public String getSpringSetting() { return usageName; }
        public static Scope get(String usageName) { return lookup.get(usageName); }
        public static String toStringOfSet() { return BeanDef.toStringOfSet(values()); }
    }

    // spring-framework constant definitions for these
    private static final int AUTOWIRE_NO            = 0;
    private static final int AUTOWIRE_BY_NAME       = 1;
    private static final int AUTOWIRE_BY_TYPE       = 2;
    private static final int AUTOWIRE_CONSTRUCTOR   = 3;
    private static final int AUTOWIRE_AUTODETECT    = 4;

    public static enum AutoWire {
        NO("no",AUTOWIRE_NO),
        BY_NAME("byName",AUTOWIRE_BY_NAME),
        BY_TYPE("byType",AUTOWIRE_BY_TYPE),
        CONSTRUCTOR("constructor",AUTOWIRE_CONSTRUCTOR),
        AUTODETECT("autodetect",AUTOWIRE_AUTODETECT);

        private static final Map<String,AutoWire> lookup = new HashMap<String,AutoWire>();
        static {
            for(AutoWire e : values())
                lookup.put(e.usageName,e);
        }
        private final String usageName;
        private final int springOrdinal;
        private AutoWire(String usageName,int springOrdinal) {
            this.usageName = usageName;
            this.springOrdinal = springOrdinal;
        }
        public String toString() { return usageName; }
        public int getSpringOrdinal() { return springOrdinal; }
        public static AutoWire get(String usageName) { return lookup.get(usageName); }
        public static String toStringOfSet() { return BeanDef.toStringOfSet(values()); }
    }

    // spring-framework constant definitions for these
    private static final int DEPENDENCY_CHECK_NONE      = 0;
    private static final int DEPENDENCY_CHECK_OBJECTS   = 1;
    private static final int DEPENDENCY_CHECK_SIMPLE    = 2;
    private static final int DEPENDENCY_CHECK_ALL       = 3;

    public static enum DependencyCheck {
        NONE("none",DEPENDENCY_CHECK_NONE),
        OBJECTS("objects",DEPENDENCY_CHECK_OBJECTS),
        SIMPLE("simple",DEPENDENCY_CHECK_SIMPLE),
        ALL("all",DEPENDENCY_CHECK_ALL);

        private static final Map<String,DependencyCheck> lookup = new HashMap<String,DependencyCheck>();
        static {
            for(DependencyCheck e : values())
                lookup.put(e.usageName,e);
        }
        private final String usageName;
        private final int springOrdinal;
        private DependencyCheck(String usageName,int springOrdinal) {
            this.usageName = usageName;
            this.springOrdinal = springOrdinal;
        }
        public String toString() { return usageName; }
        public int getSpringOrdinal() { return springOrdinal; }
        public static DependencyCheck get(String usageName) { return lookup.get(usageName); }
        public static String toStringOfSet() { return BeanDef.toStringOfSet(values()); }
    }

    //
    // template method pattern
    // http://en.wikipedia.org/wiki/Template_method_pattern
    // http://www.ajaxonomy.com/2007/java/making-the-most-of-java-50-enum-tricks
    //
    public static enum BeanCycleAttrb {
        SCOPE("scope") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                final Scope scope = Scope.get((String)beanDefAttrb);
                if (scope != null)
                    beanDef.setScope(scope);
                else
                    log.error(format(unknownAttrbErrMsgFmt,beanDefAttrb,SCOPE,beanDef.beanID));
            }
        },
        LAZY_INIT("lazy-init") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                beanDef.setLazyInit(((Boolean)beanDefAttrb).booleanValue());
            }
        },
        AUTOWIRE("autowire") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                final AutoWire aw = AutoWire.get((String)beanDefAttrb);
                if (aw != null)
                    beanDef.setAutoWire(aw);
                else
                    log.error(format(unknownAttrbErrMsgFmt,beanDefAttrb,AUTOWIRE,beanDef.beanID));
            }
        },
        DEPENDENCY_CHECK("dependency-check") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                final DependencyCheck dc = DependencyCheck.get((String)beanDefAttrb);
                if (dc != null)
                    beanDef.setDependencyCheck(dc);
                else
                    log.error(format(unknownAttrbErrMsgFmt,beanDefAttrb,DEPENDENCY_CHECK,beanDef.beanID));
            }
        },
        INIT_METHOD("init-method") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                beanDef.setInitMethod((String)beanDefAttrb);
            }
        },
        DESTROY_METHOD("destroy-method") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                beanDef.setDestroyMethod((String)beanDefAttrb);
            }
        },
        FACTORY_METHOD("factory-method") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                beanDef.setFactoryMethod((String)beanDefAttrb);
            }
        },
        FACTORY_BEAN("factory-bean") {
            @Override public void execute(BeanDef beanDef,Object beanDefAttrb) {
                beanDef.setFactoryBean((String)beanDefAttrb);
            }
        };
        public abstract void execute(BeanDef beanDef,Object beanDefAttrb);
        private static final String unknownAttrbErrMsgFmt = "\'%s\' is an unknown %s attribute on bean \'%s\'";

        private static final Map<String,BeanCycleAttrb> lookup = new HashMap<String,BeanCycleAttrb>();
        static {
            for(BeanCycleAttrb e : values())
                lookup.put(e.attrbName,e);
        }
        private final String attrbName;
        private BeanCycleAttrb(String attrbName) { this.attrbName = attrbName; }
        public String toString() { return attrbName; }
        public static BeanCycleAttrb get(String attrbName) { return lookup.get(attrbName); }
        public static String toStringOfSet() { return BeanDef.toStringOfSet(values()); }
    }

    protected static <T extends Enum<T>> String toStringOfSet(T[] set) {
        final StringBuilder sbuf = new StringBuilder();
        for(T e : set)
            sbuf.append(e.toString()).append(", ");
        sbuf.delete(sbuf.lastIndexOf(", "), sbuf.length());
        return sbuf.toString();
    }
}
